/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */

import { strict as assert } from "node:assert";

import {
	comparePersistedSchema,
	extractPersistedSchema,
	FluidClientVersion,
	FormatValidatorBasic,
	type ForestOptions,
	type ICodecOptions,
	type ImplicitFieldSchema,
	type JsonCompatible,
} from "@fluidframework/tree/alpha";

import { config, List } from "../schema.js";

import { v1 } from "./legacy/index.js";

// This file demonstrates how applications can write tests which ensure they maintain compatibility with the schema from previously released versions.

/**
 * List of schema from previous versions of this application.
 * Storing these as .json files in a folder may make more sense for more complex applications.
 *
 * The `schema` field is generated by passing the schema to `extractPersistedSchema`.
 */
const historicalSchema: {
	version: string;
	schema: JsonCompatible;
	viewSchema: ImplicitFieldSchema;
}[] = [
	{
		version: "1.0",
		schema: {
			version: 1,
			nodes: {
				"com.fluidframework.example.cli.List": {
					object: {
						"": {
							kind: "Sequence",
							types: ["com.fluidframework.leaf.string"],
						},
					},
				},
				"com.fluidframework.leaf.string": {
					leaf: 1,
				},
			},
			root: {
				kind: "Value",
				types: ["com.fluidframework.example.cli.List"],
			},
		},
		viewSchema: v1.List,
	},
	{
		version: "2.0",
		schema: {
			version: 1,
			nodes: {
				"com.fluidframework.example.cli.Item": {
					object: {
						location: {
							kind: "Value",
							types: ["com.fluidframework.example.cli.Point"],
						},
						name: {
							kind: "Value",
							types: ["com.fluidframework.leaf.string"],
						},
					},
				},
				"com.fluidframework.example.cli.List": {
					object: {
						"": {
							kind: "Sequence",
							types: ["com.fluidframework.example.cli.Item", "com.fluidframework.leaf.string"],
						},
					},
				},
				"com.fluidframework.example.cli.Point": {
					object: {
						x: {
							kind: "Value",
							types: ["com.fluidframework.leaf.number"],
						},
						y: {
							kind: "Value",
							types: ["com.fluidframework.leaf.number"],
						},
					},
				},
				"com.fluidframework.leaf.number": {
					leaf: 0,
				},
				"com.fluidframework.leaf.string": {
					leaf: 1,
				},
			},
			root: {
				kind: "Value",
				types: ["com.fluidframework.example.cli.List"],
			},
		},
		viewSchema: List,
	},
];

describe("schema", () => {
	it("current schema matches latest historical schema", () => {
		const current = extractPersistedSchema(config.schema, FluidClientVersion.v2_0, () => true);

		// For compatibility with deep equality and simple objects, round trip via JSON to erase prototypes.
		// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
		const currentRoundTripped: JsonCompatible = JSON.parse(JSON.stringify(current));

		const previous = historicalSchema.at(-1);
		assert(previous !== undefined);
		// This ensures that historicalSchema's last entry is up to date with the current application code.
		// This can catch:
		// 1. Forgetting to update historicalSchema when intentionally making schema changes.
		// 2. Accidentally changing schema in a way that impacts document compatibility.
		assert.deepEqual(currentRoundTripped, previous.schema);
	});

	describe("historical schema can be upgraded to current schema", () => {
		const options: ForestOptions & ICodecOptions = { jsonValidator: FormatValidatorBasic };

		for (let documentIndex = 0; documentIndex < historicalSchema.length; documentIndex++) {
			for (let viewIndex = 0; viewIndex < historicalSchema.length; viewIndex++) {
				// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
				it(`document ${historicalSchema[documentIndex]!.version} vs view version ${historicalSchema[viewIndex]!.version}`, () => {
					const compat = comparePersistedSchema(
						// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
						historicalSchema[documentIndex]!.schema,
						// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
						historicalSchema[viewIndex]!.viewSchema,
						options,
					);

					// We do not expect duplicates in historicalSchema.
					assert.equal(compat.isEquivalent, documentIndex === viewIndex);
					// Currently collaboration is only allowed between identical versions
					assert.equal(compat.canView, documentIndex === viewIndex);
					// Older versions should be upgradable to newer versions, but not the reverse.
					assert.equal(compat.canUpgrade, documentIndex <= viewIndex);
				});
			}
		}
	});
});
